#ifndef __material_H__
#define __material_H__

#include "rendPlatform.hpp"
#include "rendColor.hpp"
#include "rendRegister.hpp"

#include <stdio.h>
#include <vector>
#include <string>
#include <list>

namespace rend
{

class Material
{
public:

	////////////////////////////////////////////////////////////////////////
	// program register
	////////////////////////////////////////////////////////////////////////

	struct Program
	{
		GLuint name;

		enum ParameterType
		{
			PT_SAMPLER_1D,
			PT_SAMPLER_2D,
			PT_SAMPLER_3D,
			PT_SAMPLER_CUBE,
			PT_FLOAT_VEC4,
			PT_FLOAT_MAT4
		};

		struct Parameter
		{
			GLuint			location;	// location in program's local parameter file, OR texture unit index if parameter is a sampler
			GLchar			name[64];	// name to be used with parameter registers; must be unique per parameter
	 		ParameterType	type;		// parameter type; some parameter types may not apply to all programs, e.g. sampler types to vertex programs
		 	GLuint			size;		// size, in type units; size greater than 1 means parameter is an array
		};

		std::vector< Parameter > params;

		struct Attrib
		{
			GLint			location;	// location in vertex program's attribute file, i.e. index in vertex.attrib[]
			GLchar			name[16];	// name to be used with attribute buffers; must be unique per program
		};

		std::vector< Attrib > attribs;	// valid only for vertex programs; same for the corresponding helper method

		GLint getAttribLocation(const std::string& request) const;

		Program()
		: name(0)
		{}

		~Program();

		typedef naked::Register< std::string, Program > Register;

		static Register s_default_register;
	};

	static const Program* prog;		// link to the current vertex program; for use in draw() methods

	////////////////////////////////////////////////////////////////////////
	// texture register
	////////////////////////////////////////////////////////////////////////

	struct Texture
	{
		GLuint name;
		GLenum target;

		Texture()
		: name(0)
		, target(GL_TEXTURE_2D)
		{}

		~Texture();

		typedef naked::Register< std::string, Texture > Register;

		static Register s_default_register;
	};

	////////////////////////////////////////////////////////////////////////
	// various environment registers
	////////////////////////////////////////////////////////////////////////

	struct EnvMat4
	{
		matx4 datum;

		EnvMat4()
		{}

		typedef naked::Register< std::string, EnvMat4 > Register;

		static Register s_default_register;
	};

	struct EnvMat4v
	{
		enum { ARRAY_CAPACITY = 64 };

		GLuint count;
		matx4 array[ARRAY_CAPACITY];

		EnvMat4v()
		: count(0)
		{}

		typedef naked::Register< std::string, EnvMat4v > Register;

		static Register s_default_register;
	};

	struct EnvVec4
	{
		vect4 datum;

		EnvVec4()
		{}

		typedef naked::Register< std::string, EnvVec4 > Register;

		static Register s_default_register;
	};

	struct EnvVec4v
	{
		enum { ARRAY_CAPACITY = 64 };

		GLuint count;
		vect4 array[ARRAY_CAPACITY];

		EnvVec4v()
		: count(0)
		{}

		typedef naked::Register< std::string, EnvVec4v > Register;

		static Register s_default_register;
	};

	////////////////////////////////////////////////////////////////////////
	// canonical registrables
	////////////////////////////////////////////////////////////////////////

	static const std::string sn_tex_normalizer_cubemap;
	static const std::string sn_tex_exponentiation_map;

	static const std::string sn_mat4_mvp;
	static const std::string sn_mat4_mvp_inv;
	static const std::string sn_mat4_mv;
	static const std::string sn_mat4_mv_inv;
	static const std::string sn_mat4_world;
	static const std::string sn_mat4_world_inv;
	static const std::string sn_mat4_view;
	static const std::string sn_mat4_view_inv;
	static const std::string sn_mat4_proj;
	static const std::string sn_mat4_proj_inv;

	static const std::string sn_mat4v_bone;

	static const std::string sn_vec4_angular_time;
	static const std::string sn_vec4_vp_obj;
	static const std::string sn_vec4_lp_obj_0;
	static const std::string sn_vec4_lp_obj_1;
	static const std::string sn_vec4_lp_obj_2;
	static const std::string sn_vec4_lp_obj_3;
	static const std::string sn_vec4_lp_obj_4;
	static const std::string sn_vec4_lp_obj_5;
	static const std::string sn_vec4_lp_obj_6;
	static const std::string sn_vec4_lp_obj_7;

	static const std::string sn_vec4v_lp_obj;

	////////////////////////////////////////////////////////////////////////
	// single draw pass
	////////////////////////////////////////////////////////////////////////

	class Pass
	{
	public:
		Pass();
		Pass(const Pass& src);
		Pass& operator =(const Pass& src);
		~Pass();

		bool					save(FILE* f);
		bool					load(FILE* f);

		bool					validate();
		void					invalidate();
		void					actuate();

		enum BlendFactor
		{
			BF_ZERO		= 0,			// (  0,  0,  0,  0 )
			BF_ONE,						// (  1,  1,  1,  1 )
			BF_OTHER_COLOR,				// ( Ro, Go, Bo, Ao ), where 'other' is dst for sfactor, and src for dfactor, respectively
			BF_ONE_MINUS_OTHER_COLOR,	// (  1,  1,  1,  1 ) - ( Ro, Go, Bo, Ao )
			BF_SRC_ALPHA,				// ( As, As, As, As )
			BF_ONE_MINUS_SRC_ALPHA,		// (  1,  1,  1,  1 ) - ( As, As, As, As )
			BF_DST_ALPHA,				// ( Ad, Ad, Ad, Ad )
			BF_ONE_MINUS_DST_ALPHA		// (  1,  1,  1,  1 ) - ( Ad, Ad, Ad, Ad )
		};

		void					setBlendFactors(const BlendFactor s, const BlendFactor d);
		void					getBlendFactors(BlendFactor& s, BlendFactor& d) const;

		bool					isBlending() const;
		
		void					setNameVertProg(const std::string& name);
		const std::string&		getNameVertProg() const;

		void					setNameFragProg(const std::string& name);
		const std::string&		getNameFragProg() const;

		////////////////////////////////////////////////////////////////////////
		// misc statics
		////////////////////////////////////////////////////////////////////////

		// resolution must be power of two
		static bool createNormalizerCubemap(
			Texture::Register* reg_tex,
			const std::string& name,
			const unsigned resolution = 256);

		// implied exp equals exp_res, spans [1, +inf); implied base spans [0, 1], base_res spans [2, +inf)
		static bool createExponentiationMap(
			Texture::Register* reg_tex,
			const std::string& name,
			const unsigned exp_res,
			const unsigned base_res = 256);

		static bool createTexture2DFromFile(
			Texture::Register* reg_tex,
			const std::string& name,
			const std::string& filename);

		static bool createVertexProgram(
			Program::Register* reg_prog,
			const std::string& name,
			GLuint& native_name);

		static bool createFragmentProgram(
			Program::Register* reg_prog,
			const std::string& name,
			GLuint& native_name);

		////////////////////////////////////////////////////////////////////////
		// interface for Material
		////////////////////////////////////////////////////////////////////////

		void setParent(Material*);

	private:

		static bool load(
			const std::string& fname,
			std::string& content);

		enum ProgramType
		{
			PROG_VERT = 0,
			PROG_FRAG,

			PROG_TYPE_COUNT
		};

		bool validateProgram(
			const std::string& name,
			const ProgramType prog_type);

		void actuateProgram(
			const ProgramType prog_type);

		Material*				m_parent;

		std::string				m_name_vprog;
		std::string				m_name_fprog;

		BlendFactor				m_blend_src;
		BlendFactor				m_blend_dst;

		GLuint					m_prog[PROG_TYPE_COUNT];

		std::vector< std::pair< GLuint, const Texture* > >							m_uni_tex;
		std::vector< std::pair< GLuint, const EnvVec4* > >							m_uni_vec4[PROG_TYPE_COUNT];
		std::vector< std::pair< std::pair< GLuint, GLuint >, const EnvVec4v* > >	m_uni_vec4v[PROG_TYPE_COUNT];
		std::vector< std::pair< GLuint, const EnvMat4* > >							m_uni_mat4[PROG_TYPE_COUNT];
		std::vector< std::pair< std::pair< GLuint, GLuint >, const EnvMat4v* > >	m_uni_mat4v[PROG_TYPE_COUNT];
	};

	Material();
	Material(const Material& src);

	Material& operator =(const Material& src);

	~Material();

	bool						save(FILE* f);
	bool						load(FILE* f);

	bool						save(const std::string& fname);
	bool						load(const std::string& fname);

	void						setProgramRegister(Program::Register*);
	Program::Register*			getProgramRegister() const;

	////////////////////////////////////////////////////////////////////////
	// warning: do not remove entries from registers while those are in use
	// by a material
	////////////////////////////////////////////////////////////////////////

	void					insertTextureRegister(Texture::Register*, unsigned position);
	void					removeTextureRegister(Texture::Register*);
	Texture::Register*		getFirstTextureRegister() const;
	Texture::Register*		getNextTextureRegister() const;

	void					insertMat4Register(EnvMat4::Register*, unsigned position);
	void					removeMat4Register(EnvMat4::Register*);
	EnvMat4::Register*		getFirstMat4Register() const;
	EnvMat4::Register*		getNextMat4Register() const;

	void					insertMat4vRegister(EnvMat4v::Register*, unsigned position);
	void					removeMat4vRegister(EnvMat4v::Register*);
	EnvMat4v::Register*		getFirstMat4vRegister() const;
	EnvMat4v::Register*		getNextMat4vRegister() const;

	void					insertVec4Register(EnvVec4::Register*, unsigned position);
	void					removeVec4Register(EnvVec4::Register*);
	EnvVec4::Register*		getFirstVec4Register() const;
	EnvVec4::Register*		getNextVec4Register() const;

	void					insertVec4vRegister(EnvVec4v::Register*, unsigned position);
	void					removeVec4vRegister(EnvVec4v::Register*);
	EnvVec4v::Register*		getFirstVec4vRegister() const;
	EnvVec4v::Register*		getNextVec4vRegister() const;

	void					setNumPasses(const unsigned numPasses);
	unsigned				getNumPasses() const;

	Pass&					getPass(const unsigned pass_i);

	bool					isBlending() const;

	bool					validate();
	void					invalidate();
	void					actuate(const unsigned pass_i);

	void					prologue();
	void					epilogue();

protected:

	std::vector< Pass >		m_passes;

	Program::Register*					m_reg_prog;

	std::list< Texture::Register* >		m_reg_tex;
	std::list< EnvMat4::Register* >		m_reg_mat4;
	std::list< EnvMat4v::Register* >	m_reg_mat4v;
	std::list< EnvVec4::Register* >		m_reg_vec4;
	std::list< EnvVec4v::Register* >	m_reg_vec4v;

	mutable std::list< Texture::Register* >::const_iterator		m_regit_tex;
	mutable std::list< EnvMat4::Register* >::const_iterator		m_regit_mat4;
	mutable std::list< EnvMat4v::Register* >::const_iterator	m_regit_mat4v;
	mutable std::list< EnvVec4::Register* >::const_iterator		m_regit_vec4;
	mutable std::list< EnvVec4v::Register* >::const_iterator	m_regit_vec4v;
};


inline
Material::Program::~Program()
{
	if (name)
	{
		printf("discarding program id 0x%08x\n", name);

		glDeleteProgramsARB(1, &name);

		if (reportGLError(stderr))
			fprintf(stderr, "failure deleting program\n");
	}
}


inline GLint
Material::Program::getAttribLocation(const std::string& request) const
{
	std::vector< Attrib >::const_iterator it = attribs.begin();
	const std::vector< Attrib >::const_iterator iend = attribs.end();

	for ( ; it != iend; ++it)
		if (request == it->name)
			return it->location;

	return -1;
}


inline
Material::Texture::~Texture()
{
	if (name)
	{
		printf("discarding texture id 0x%08x\n", name);

		glDeleteTextures(1, &name);

		if (reportGLError(stderr))
			fprintf(stderr, "failure deleting texture\n");
	}
}


inline void
Material::Pass::invalidate()
{
	for (unsigned i = 0; i < PROG_TYPE_COUNT; ++i)
		m_prog[i] = 0;
}


inline void
Material::Pass::setParent(
	Material* parent)
{
	assert(parent);

	m_parent = parent;

	invalidate();
}


inline unsigned
Material::getNumPasses() const
{
	return m_passes.size();
}


inline bool
Material::isBlending() const
{
	if (!m_passes.empty())
		return m_passes.front().isBlending();

	return false;
}

} // namespace rend

#endif // __material_H__
